
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Xunit;
using Xunit.Extensions;


namespace Boilen.Primitives {

    public class TestXmlDocumentation {

        [Fact]
        public void CombinePath_fails_for_null_segments( ) {
            string[] pathSegments = null;

            Assert.Throws<ArgumentNullException>( ( ) => XmlDocumentation.CombinePath( pathSegments ) );
        }

        [Fact]
        public void CombinePath_fails_for_empty_segments( ) {
            string[] pathSegments = new string[0];

            Assert.Throws<ArgumentException>( ( ) => XmlDocumentation.CombinePath( pathSegments ) );
        }

        [Theory]
        [InlineData( (object)new[] { "a" } )]
        [InlineData( (object)new[] { "a", "b" } )]
        [InlineData( (object)new[] { "a", "", "b" } )]
        [InlineData( (object)new[] { "a", "b", "c" } )]
        [InlineData( (object)new[] { @"C:\ParentDir", "ChildDir" } )]
        public void CombinePath_succeeds_for_valid_segments( string[] pathSegments ) {
            string pathSeparator = System.IO.Path.DirectorySeparatorChar.ToString( );
            string[] validPathSegments = pathSegments.Where( s => s.Length > 0 ).ToArray( );
            string expectedPath = string.Join( pathSeparator, validPathSegments );

            string actualPath = XmlDocumentation.CombinePath( pathSegments );

            Assert.Equal( actualPath, expectedPath );
        }


        [Fact]
        public void GetMemberId_fails_for_null_member( ) {
            MemberInfo member = null;

            Assert.Throws<ArgumentNullException>( ( ) => XmlDocumentation.GetMemberId( member ) );
        }

        [Theory]
        [PropertyData( "Members" )]
        public void GetMemberId_succeeds_for_non_null_member( MemberInfo member ) {
            string memberId = XmlDocumentation.GetMemberId( member );

            Assert.NotEmpty( memberId );
        }

        [Theory]
        [PropertyData( "Members" )]
        public void GetMemberId_uses_correct_prefix( MemberInfo member ) {
            string expectedPrefix = GetMemberIdPrefix( member );

            string memberId = XmlDocumentation.GetMemberId( member );

            AssertExtensions.StartsWith( memberId, expectedPrefix );
        }

        [Theory]
        [PropertyData( "Members" )]
        public void GetMemberId_uses_full_member_name( MemberInfo member ) {
            string expectedName = GetMemberName( member );

            string memberId = XmlDocumentation.GetMemberId( member );
            string memberIdWithoutPrefix = memberId.Substring( 2 );

            AssertExtensions.StartsWith( memberIdWithoutPrefix, expectedName );
        }

        [Theory]
        [PropertyData( "Members" )]
        public void GetMemberId_uses_member_arguments( MemberInfo member ) {
            int expectedNameLength = GetMemberName( member ).Length;
            string expectedArguments = GetMemberArguments( member );

            string memberId = XmlDocumentation.GetMemberId( member );
            string memberIdWithoutName = memberId.Substring( expectedNameLength + 2 );

            Assert.Equal( memberIdWithoutName, expectedArguments );
        }


        [Fact]
        public void HasXmlDocumentation_fails_for_null_assembly( ) {
            Assembly assembly = null;

            Assert.Throws<ArgumentNullException>( ( ) => XmlDocumentation.HasXmlDocumentation( assembly ) );
        }

        [Fact]
        public void HasXmlDocumentation_returns_false_for_undocumented_assembly( ) {
            Assembly assembly = this.GetType( ).Assembly;

            bool result = XmlDocumentation.HasXmlDocumentation( assembly );

            Assert.False( result );
        }

        [Theory]
        [PropertyData( "Members" )]
        public void HasXmlDocumentation_returns_true_for_system_members( MemberInfo member ) {
            Assembly assembly = (member.DeclaringType ?? (Type)member).Assembly;

            bool result = XmlDocumentation.HasXmlDocumentation( assembly );

            Assert.True( result );
        }


        [Fact]
        public void GetDocMember_fails_for_null_member( ) {
            MemberInfo member = null;

            Assert.Throws<ArgumentNullException>( ( ) => XmlDocumentation.GetDocMember( member ) );
        }

        [Fact]
        public void GetDocMember_fails_for_undocumented_member( ) {
            MemberInfo member = this.GetType( );

            Assert.Throws<ArgumentException>( ( ) => XmlDocumentation.GetDocMember( member ) );
        }

        [Theory]
        [PropertyData( "Members" )]
        public void GetDocMember_succeeds_for_system_members( MemberInfo member ) {
            var doc = XmlDocumentation.GetDocMember( member );

            string memberName = member.Name.TrimStart( '.' );
            Assert.Contains( memberName, doc.Attribute( "name" ).Value );
        }


        #region Utility

        private static readonly MemberInfo[] testMembers_;

        static TestXmlDocumentation( ) {
            Type systemType = typeof( System.Diagnostics.PerformanceCounter );
            Type wpfType = typeof( System.Windows.FrameworkContentElement );
            Type staticType = typeof( System.Linq.Enumerable );
            Type nestedType = typeof( System.Environment.SpecialFolder );
            Type genericType = typeof( System.Collections.Generic.List<> );

            testMembers_ = new MemberInfo[] {
                systemType,
                systemType.GetField( "DefaultFileMappingSize" ),
                systemType.GetProperty( "CategoryName", typeof( string ) ),
                systemType.GetEvent( "Disposed" ),
                systemType.GetConstructor( Type.EmptyTypes ),
                systemType.GetConstructor( new[] { typeof( string ), typeof( string ) } ),
                systemType.GetMethod( "Close", Type.EmptyTypes ),
                systemType.GetMethod( "IncrementBy", new[] { typeof( long ) } ),
                wpfType,
                wpfType.GetField( "TagProperty" ),
                wpfType.GetProperty( "Tag", typeof( object ) ),
                wpfType.GetEvent( "Loaded" ),
                wpfType.GetConstructor( Type.EmptyTypes ),
                wpfType.GetMethod( "BeginInit", Type.EmptyTypes ),
                wpfType.GetMethod( "GetBindingExpression", new[] { typeof( System.Windows.DependencyProperty ) } ),
                staticType,
                staticType.GetMethod( "Repeat" ),
                nestedType,
                nestedType.GetField( "Desktop" ),
                genericType,
                //TODO: add support for mixed generic methods ("M:System.Collections.Generic.List`1.ConvertAll``1(System.Converter{`0,``0})"): genericType.GetMethod( "ConvertAll" )
            };
        }

        public static IEnumerable<object[]> Members {
            get {
                for( int i = 0; i < testMembers_.Length; ++i ) {
                    MemberInfo member = testMembers_[i];
                    Assert.NotNull( member );
                    yield return new object[] { member };
                }
            }
        }


        private static string GetMemberIdPrefix( MemberInfo member ) {
            if( member is Type )
                return "T:";
            if( member is FieldInfo )
                return "F:";
            if( member is PropertyInfo )
                return "P:";
            if( member is EventInfo )
                return "E:";
            return "M:";
        }

        private static string GetMemberName( MemberInfo member ) {
            if( member == null )
                return "";
            else if( member is Type && ((Type)member).IsGenericParameter ) {
                Type genericParameter = (Type)member;
                Type[] methodParameters = genericParameter.DeclaringMethod != null
                    ? genericParameter.DeclaringMethod.GetGenericArguments( )
                    : null;
                int parameterIndex = Array.IndexOf( methodParameters ?? genericParameter.DeclaringType.GetGenericArguments( ), genericParameter );
                string prefix = methodParameters == null ? "`" : "``";
                return prefix + parameterIndex;
            }


            string declaringTypeFullName = GetMemberName( member.DeclaringType );
            if( declaringTypeFullName.Length == 0 && member is Type )
                declaringTypeFullName = ((Type)member).Namespace;

            string separator = declaringTypeFullName.Length > 0 ? "." : "";

            string memberName = member.Name;
            if( member is ConstructorInfo )
                memberName = ((ConstructorInfo)member).IsStatic ? "#cctor" : "#ctor";
            else if( member is MethodInfo )
                memberName += ("``" + ((MethodInfo)member).GetGenericArguments( ).Length).Replace( "``0", "" );

            string memberFullName = declaringTypeFullName + separator + memberName;
            return memberFullName;
        }

        private static string GetMemberArguments( MemberInfo member ) {
            var parameters = member is MethodBase
                ? ((MethodBase)member).GetParameters( )
                : new ParameterInfo[0];

            if( parameters.Length == 0 )
                return "";

            return "(" + Util.Join( parameters, ",", ( i, p ) => GetMemberName( p.ParameterType ) ) + ")";
        }

        #endregion

    }

}
